import { Cell, Edge, Graph, Path } from '@antv/x6';
import { PortManager } from '@antv/x6/lib/model/port';

import { BaseNodeProps, NodeDataTypes } from '@/components/complex/BaseNode/define';
import { CriteriaItem, LogicExpression } from '@/components/complex/Rules';
import { colors } from '@/config/theme';
import { CACHE_GRAPH_DATA, NODE_LIST, NODE_TYPE } from '@/constants/config';
import { ResultItem } from '@/store/node-results';

import storage from './storage';

export const createPortNameWithNodeType = (nodeType: keyof typeof NODE_TYPE, direction: 'left' | 'right') => {
  return nodeType + '_port_' + direction;
};

export const createGraphNode = (nodeType: keyof typeof NODE_TYPE, data: any) => {
  const ports: Partial<PortManager.Metadata> | PortManager.PortMetadata[] = [
    {
      id: createPortNameWithNodeType(nodeType, 'left'),
      group: 'in'
    },
    {
      id: createPortNameWithNodeType(nodeType, 'right'),
      group: 'out'
    }
  ];
  if (nodeType === NODE_TYPE.START) {
    ports.splice(0, 1);
  } else if (nodeType === NODE_TYPE.END) {
    ports.splice(1, 1);
  }

  const initData = {
    ...data,
    nodeType
  } as any;

  if (nodeType in NODE_LIST) {
    Object.assign(initData, NODE_LIST[nodeType]);
  }

  return {
    shape: nodeType,
    data: initData,
    ports
  };
};

export const getGraphJsonFromStorage = (id: string) => {
  const cacheKey = `${CACHE_GRAPH_DATA}-${id}`;
  return storage.get(cacheKey) as {
    cells: Cell.Properties[];
  };
};

export const setGraphJsonToStorage = (
  id: string,
  json: {
    cells: Cell.Properties[];
  }
) => {
  const cacheKey = `${CACHE_GRAPH_DATA}-${id}`;

  storage.set(cacheKey, json);
};

export const removeGraphJsonFromStorage = (id: string) => {
  const cacheKey = `${CACHE_GRAPH_DATA}-${id}`;
  storage.remove(cacheKey);
};

export const defaultEdgeAttrs = {
  wrap: {
    connection: true,
    strokeWidth: 10,
    strokeLinejoin: 'round'
  },
  line: {
    connection: true,
    stroke: colors.gray[400],
    strokeWidth: 1,
    targetMarker: {
      name: 'classic',
      size: 6
    }
  }
};

export const registerEdge = () => {
  Graph.registerConnector(
    'curveConnector',
    (sourcePoint, targetPoint) => {
      const hGap = Math.abs(targetPoint.x - sourcePoint.x);
      const path = new Path();
      path.appendSegment(Path.createSegment('M', sourcePoint.x - 4, sourcePoint.y));
      path.appendSegment(Path.createSegment('L', sourcePoint.x + 12, sourcePoint.y));
      // 水平三阶贝塞尔曲线
      path.appendSegment(
        Path.createSegment(
          'C',
          sourcePoint.x < targetPoint.x ? sourcePoint.x + hGap / 2 : sourcePoint.x - hGap / 2,
          sourcePoint.y,
          sourcePoint.x < targetPoint.x ? targetPoint.x - hGap / 2 : targetPoint.x + hGap / 2,
          targetPoint.y,
          targetPoint.x - 6,
          targetPoint.y
        )
      );
      path.appendSegment(Path.createSegment('L', targetPoint.x + 2, targetPoint.y));

      return path.serialize();
    },
    true
  );

  Edge.config({
    markup: [
      {
        tagName: 'path',
        selector: 'wrap',
        attrs: {
          fill: 'none',
          cursor: 'pointer',
          stroke: 'transparent',
          strokeLinecap: 'round'
        }
      },
      {
        tagName: 'path',
        selector: 'line',
        attrs: {
          fill: 'none',
          pointerEvents: 'none'
        }
      }
    ],
    connector: 'smooth',
    attrs: {
      ...defaultEdgeAttrs
    }
  });

  Graph.registerEdge('BASE_EDGE', Edge, true);
};

export const findParentNode = (graph: Graph, node: any) => {
  const parent = graph.getCellById(node.parent);
  if (parent) {
    return parent;
  }
  return null;
};

/**
 * 查找父级节点的变量
 * @param graph 实例
 * @param nodeId id
 */
export const findParentVariables = (graph: Graph | null, nodeId: string, variableType: 'private' | 'public') => {
  const result: VariableItem[] = [];

  const getEdgeByTargetId = (targetId: string) => {
    return graph!.getEdges()?.find((item) => item.getTargetCell()?.id === targetId);
  };
  const setVariablesByNodeId = (id: string) => {
    const variables = findVariables(graph, id, variableType);
    result.push(...variables);
  };
  const findParent = (id: string, noSave = false) => {
    const node = graph?.getCellById(id);
    if (node && node.shape) {
      if (node.shape === 'BASE_EDGE') {
        if (!noSave) {
          setVariablesByNodeId(id);
        }
        const sourceId = (node as Edge<Edge.Properties>).getSourceCell()?.id;
        findParent(sourceId!);
      } else {
        if (!noSave) {
          setVariablesByNodeId(id);
        }
        const edge = getEdgeByTargetId(id);
        if (edge) {
          findParent(edge.id);
        }
      }
    }
  };

  findParent(nodeId, true);

  return result;
};

/**
 * 查找父级节点的变量
 * @param graph 实例
 * @param nodeId id
 */
export const findVariables = (graph: Graph | null, nodeId: string, variableType: 'private' | 'public') => {
  const data = graph?.getCellById(nodeId!)?.getData<BaseNodeProps<any>>();
  return (variableType === 'private' ? data?.privateVariables : data?.publicVariables) || [];
};

export const getAllVariables = (graph: Graph | null, variableType: 'private' | 'public') => {
  const cells = graph?.getCells();
  const variables: VariableItem[] = [];

  cells?.map((cell) => {
    const data = cell.getData<BaseNodeProps<any>>();

    if (variableType === 'private') {
      return variables.push(...(data?.privateVariables?.filter(Boolean) || []));
    }

    return variables.push(...(data?.publicVariables?.filter(Boolean) || []));
  });

  return variables;
};

/** 根据执行结果展示结果样式 */
export const showNodeStatusWithState = (graph: Graph, data: ResultItem) => {
  const isNode = !!data.nodeId;
  if (isNode) {
    graph?.getCellById(data.nodeId!)?.setData({
      status: data.execStatus
    });
    if (!data.success || data.execStatus !== 'FINISHED') {
      return;
    }
    graph.getEdges().forEach((edge) => {
      if (edge.getSourceCellId() === data.nodeId) {
        edge.attr({
          line: {
            stroke: colors.blue[500],
            strokeDasharray: 5,
            style: {
              animation: 'running-line 10s infinite linear'
            }
          }
        });
      }
    });
  } else {
    const edge = graph?.getCellById(data.edgeId!) as Edge;
    edge?.attr({
      line: {
        stroke: data.pass ? colors.green[500] : colors.red[500],
        strokeDasharray: 0,
        strokeWidth: 1.5,
        style: {
          animation: ''
        }
      }
    });

    edge?.getTargetNode()?.setData({
      status: 'RUNNING'
    });
  }
};

export const setEdgeStyle = (edge: Edge, isError?: boolean) => {
  edge?.attr({
    line: {
      stroke: !isError ? colors.gray[400] : colors.red[600],
      strokeDasharray: 0,
      strokeWidth: 1.5,
      style: {
        animation: ''
      }
    }
  });
};

export const setEdgeLabel = (edge: Edge, labels?: string | string[], isError?: boolean) => {
  const text = Array.isArray(labels) ? labels.join('\n') : labels;
  setEdgeStyle(edge, isError);

  if (!text) {
    edge.setLabels([]);
    return;
  }

  edge?.setLabels([
    {
      attrs: {
        label: { text },
        text: {
          fill: isError ? colors.red[600] : colors.blue[600],
          fontSize: 12,
          lineHeight: 16,
          textAnchor: 'middle',
          textVerticalAnchor: 'middle'
        },
        rect: {
          ref: 'label',
          fill: isError ? colors.red[100] : colors.blue[100],
          stroke: isError ? colors.red[400] : colors.blue[400],
          refWidth: 14,
          refHeight: 5,
          refX: -7,
          refY: -2.5,
          rx: 20,
          ry: 20
        }
      },
      position: {
        distance: 0.5
      }
    }
  ]);
};

export const toDefaultCellStyle = (graph: Graph | null) => {
  graph?.getNodes().forEach((item) => {
    item.setData({ status: '' });
  });

  graph?.getEdges().forEach((edge) => {
    setEdgeStyle(edge);
    const criteria = edge.getData()?.data?.criteria;
    if(criteria) {
      const label = formatConditions(criteria!);
      const node = graph?.getCellById(edge.id!) as Edge;
      setEdgeLabel(node, label, false);
    }
  });
};

interface IEdgeStatus {
  edgeId: string;
  status?: BaseNodeProps<any>['status'];
  labels?: string[];
}
/** 根据校验结果展示结果样式 */
export const showEdgeStatusWithVerification = (graph: Graph, edgeOpts: IEdgeStatus) => {
  const { edgeId, status, labels } = edgeOpts;
  const edge = graph?.getCellById(edgeId!) as Edge;
  const isError = status === 'FAILED';
  setEdgeStyle(edge, isError);

  if (labels?.length) {
    setEdgeLabel(edge, labels, isError);
  }
};

export const showNodeStatusWithVerification = (graph: Graph, nodeId: string, status?: BaseNodeProps<any>['status']) => {
  graph?.getCellById(nodeId!)?.setData?.({
    status
  });
  if (status === 'WAITING') {
    graph.getEdges().forEach((edge) => {
      if (edge.getSourceCellId() === nodeId) {
        edge.attr({
          line: {
            stroke: '#3471F9',
            strokeDasharray: 5,
            style: {
              animation: 'running-line 10s infinite linear'
            }
          }
        });
      }
    });
  }
};

// edge边数据处理
const codeToOperatorMapping: Record<string, string> = {
  GT: '>',
  GE: '>=',
  LT: '<',
  LE: '<=',
  EQ: '==',
  NEQ: '!=',
  IN: 'in',
  NOT_IN: 'not in',
  LIKE: 'like',
  NOT_LIKE: 'not like',
  IS_EMPTY: 'is empty',
  IS_NOT_EMPTY: 'is not empty'
};
/* {
  type: 'Criteria',
  conj: 'AND',
  conditions: [
    { type: 'Criterion', left: 'a', operator: 'EQ', right: '1', rightType: 'CONSTANT' },
    { type: 'Criterion', left: 'b', operator: 'LT', right: 'a', rightType: 'VARIABLE' },
    {
      type: 'Criteria',
      conj: 'OR',
      conditions: [
        { type: 'Criterion', left: 'c', operator: 'LT', right: '3', rightType: 'CONSTANT' },
        {
          type: 'Criteria',
          conj: 'AND',
          conditions: [{ type: 'Criterion', left: 'd', operator: 'EQ', right: '4', rightType: 'CONSTANT' }]
        }
      ]
    }
  ]
};

to

a == 1 && b < a || (c < 3 && (d == 4))
*/

export const formatConditions = (conditions: CriteriaItem): string => {
  if (!conditions) {
    return '';
  }
  const { type, conj, conditions: subConditions } = conditions;
  if (type === 'Criterion') {
    const { left, operator, right } = conditions as LogicExpression;
    return `${left} ${codeToOperatorMapping[operator!]} ${right}`;
  }
  if (type === 'Criteria') {
    if (subConditions?.length === 1) {
      return formatConditions(subConditions[0]);
    }
    const conjStr = conj!.toLowerCase() === 'and' ? '&&' : '||';
    if (subConditions?.length) {
      return `(${subConditions?.map(formatConditions).join(` ${conjStr} `)})`;
    }
  }
  return '';
};

export const formatEdgesForSave = (cells: Cell.Properties[]) => {
  const edges = cells.filter((item) => item.shape === 'BASE_EDGE') || [];
  return edges.map((edge) => {
    const info = {
      fromId: edge.source.cell as unknown as string,
      toId: edge.target.cell as unknown as string,
      id: edge.id
    } as Record<string, any>;

    const criteria = edge?.data?.data?.criteria;
    if (criteria) {
      info.criteria = criteria;
    }
    return info;
  });
};

export const formatNodesForSave = (cells: Cell.Properties[]) => {
  const nodes = cells.filter((item) => item.shape !== 'BASE_EDGE') || [];
  return nodes.map((node) => {
    const nodeType = node.shape as keyof typeof NODE_TYPE;
    const nodeData = node?.data as BaseNodeProps<any>;
    const name = nodeData.nodeName || nodeType;
    const nodeId = node.id as unknown as string;
    const variables = nodeData.privateVariables || [];
    const info = {
      name,
      nodeType,
      id: nodeId,
      blocked: nodeData.blocked
    } as Record<string, any>;
    if (nodeType === NODE_TYPE.DATA_LOAD) {
      const tempData = nodeData.data as NodeDataTypes[NODE_TYPE.DATA_LOAD];
      Object.assign(info, {
        loadSpec: {
          dataSource: tempData?.dataSource,
          tableName: tempData?.tableName,
          columns: tempData?.columns,
          criteria: tempData?.criteria
        }
      });
    } else if (nodeType === NODE_TYPE.RULE) {
      const tempData = nodeData.data as NodeDataTypes[NODE_TYPE.RULE];
      const flowRules = tempData?.rulesList?.map((item, index) => {
        return {
          priority: index + 1,
          ruleName: String(index + 1),
          criteria: item.criteria,
          assignments: Array.isArray(item.assignments) ? item.assignments : [item.assignments]
        };
      });
      Object.assign(info, {
        flowRules: {
          enableListOperator: tempData?.enableListOperator,
          listOperator: tempData?.listOperator,
          flowRules: flowRules
        }
      });
    } else if (nodeType === NODE_TYPE.API) {
      const tempData = nodeData.data as NodeDataTypes[NODE_TYPE.API];
      Object.assign(info, {
        apiId: tempData?.actionId
      });
    } else if (nodeType === NODE_TYPE.DATA_MODIFY) {
      const tempData = nodeData.data as NodeDataTypes[NODE_TYPE.DATA_MODIFY];
      Object.assign(info, {
        storeSpec: {
          criteria: tempData?.criteria,
          mapping: tempData?.mapping,
          dataSource: tempData?.dataSource,
          tableName: tempData?.tableName
        }
      });
    } else if ([NODE_TYPE.START, NODE_TYPE.END].includes(nodeType as any)) {
      delete info.blocked;
    }

    if (variables?.length) {
      info.variables = variables;
    }
    return info;
  }) as any[];
};
